<?php declare(strict_types=1);

namespace Shopware\Tests\Unit\Core\Framework\Rule;

use PHPUnit\Framework\Attributes\CoversClass;
use PHPUnit\Framework\Attributes\DataProvider;
use PHPUnit\Framework\TestCase;
use Shopware\Core\Framework\Log\Package;
use Shopware\Core\Framework\Rule\DateRangeRule;
use Shopware\Core\Framework\Rule\RuleException;
use Shopware\Core\Framework\Rule\RuleScope;
use Symfony\Component\Validator\Validation;

/**
 * @internal
 */
#[Package('fundamentals@after-sales')]
#[CoversClass(DateRangeRule::class)]
class DateRangeRuleTest extends TestCase
{
    #[DataProvider('matchDataProvider')]
    public function testMatch(
        ?string $fromDate,
        ?string $toDate,
        bool $useTime,
        ?string $timezone,
        string $now,
        bool $expectedResult
    ): void {
        $rule = new DateRangeRule(
            $fromDate ? new \DateTime($fromDate) : null,
            $toDate ? new \DateTime($toDate) : null,
            $useTime,
            $timezone ? new \DateTimeZone($timezone) : null,
        );
        $scopeMock = $this->createMock(RuleScope::class);
        $scopeMock->method('getCurrentTime')->willReturn(new \DateTimeImmutable($now));

        $matchResult = $rule->match($scopeMock);

        static::assertSame($expectedResult, $matchResult);
    }

    /**
     * @return array<int, array<int, bool|string|null>>
     */
    public static function matchDataProvider(): array
    {
        return [
            // from and to set, useTime = false
            [
                '2021-01-01 00:00:00',
                '2021-01-01 00:00:00',
                false,
                null,
                '2021-01-01 00:00:00',
                true,
            ],
            [
                '2021-01-01 00:00:00',
                '2021-01-01 00:00:00',
                false,
                null,
                '2020-12-31 23:59:59',
                false,
            ],
            [
                '2021-01-01 00:00:00',
                '2021-01-01 00:00:00',
                false,
                null,
                '2021-01-01 23:59:59',
                true,
            ],
            [
                '2021-01-01 00:00:00',
                '2021-01-01 00:00:00',
                false,
                null,
                '2021-01-02 00:00:00',
                false,
            ],
            [
                '2021-01-01 11:00:00',
                '2021-01-02 10:00:00',
                false,
                null,
                '2021-01-01 10:00:00',
                true,
            ],
            [
                '2021-01-01 11:00:00',
                '2021-01-02 10:00:00',
                false,
                null,
                '2021-01-02 10:00:00',
                true,
            ],
            [
                '2021-01-01 11:00:00',
                '2021-01-02 10:00:00',
                false,
                null,
                '2021-01-03 10:00:00',
                false,
            ],

            // from and to set, useTime = true
            [
                '2021-01-01 00:00:00',
                '2021-01-01 10:00:00',
                true,
                null,
                '2021-01-01 00:00:00',
                true,
            ],
            [
                '2021-01-01 00:00:00',
                '2021-01-01 10:00:00',
                true,
                null,
                '2020-12-31 23:59:59',
                false,
            ],
            [
                '2021-01-01 00:00:00',
                '2021-01-01 10:00:00',
                true,
                null,
                '2021-01-01 09:59:59',
                true,
            ],
            [
                '2021-01-01 00:00:00',
                '2021-01-01 10:00:00',
                true,
                null,
                '2021-01-01 10:00:00',
                false,
            ],

            // only from set, useTime = false
            [
                '2021-01-01 00:00:00',
                null,
                false,
                null,
                '2021-01-01 00:00:00',
                true,
            ],
            [
                '2021-01-01 00:00:00',
                null,
                false,
                null,
                '2020-12-31 23:59:59',
                false,
            ],

            // only from set, useTime = true
            [
                '2021-01-01 00:00:00',
                null,
                true,
                null,
                '2021-01-01 00:00:00',
                true,
            ],
            [
                '2021-01-01 00:00:00',
                null,
                true,
                null,
                '2020-12-31 23:59:59',
                false,
            ],

            // only to set, useTime = false
            [
                null,
                '2021-01-01 00:00:00',
                false,
                null,
                '2021-01-01 23:59:59',
                true,
            ],
            [
                null,
                '2021-01-01 00:00:00',
                false,
                null,
                '2021-01-02 00:00:00',
                false,
            ],

            // Some timezone checks

            // with useTime = false
            [
                '2021-01-01 10:00:00',
                '2021-01-01 20:00:00',
                true,
                'UTC',
                '2021-01-01 20:00:00 -01:00',
                false,
            ],
            [
                '2021-01-01 10:00:00',
                '2021-01-01 20:00:00',
                true,
                'UTC',
                '2021-01-01 20:00:00 +01:00',
                true,
            ],
            [
                '2021-01-01 00:00:00',
                '2021-01-01 00:00:00',
                false,
                'UTC',
                '2021-01-02 02:00:00 +04:00',
                true,
            ],
            [
                '2021-01-02 00:00:00',
                '2021-01-02 00:00:00',
                false,
                'Etc/GMT-2',
                '2021-01-01 22:00:00',
                true,
            ],
            [
                '2021-01-02 00:00:00',
                '2021-01-02 00:00:00',
                false,
                'Etc/GMT-2',
                '2021-01-01 21:59:59',
                true,
            ],
            // with useTime = true
            [
                '2021-01-01 10:00:00',
                '2021-01-01 20:00:00',
                true,
                'Etc/GMT-2',
                '2021-01-01 08:00:00',
                true,
            ],
            [
                '2021-01-01 10:00:00',
                '2021-01-01 20:00:00',
                true,
                'Etc/GMT-2',
                '2021-01-01 07:59:59',
                false,
            ],

            // nothing set
            [
                null,
                null,
                true,
                null,
                '2021-01-01 07:59:59',
                true,
            ],
        ];
    }

    public function testSerializationAndValidation(): void
    {
        $rule = new DateRangeRule(
            new \DateTime('2024-01-15 10:30:45'),
            new \DateTime('2024-01-31 23:59:59'),
            true,
            new \DateTimeZone('UTC')
        );

        $serialized = json_encode($rule);

        static::assertIsString($serialized);

        $data = json_decode($serialized, true);

        static::assertSame('2024-01-15T10:30:45', $data['fromDate']);
        static::assertSame('2024-01-31T23:59:59', $data['toDate']);

        $validator = Validation::createValidatorBuilder()->enableAttributeMapping()->getValidator();
        $constraints = $rule->getConstraints();

        static::assertCount(0, $validator->validate($data['fromDate'], $constraints['fromDate']));
        static::assertCount(0, $validator->validate($data['toDate'], $constraints['toDate']));
    }

    public function testAssignWithStringDatesConvertsToDateTime(): void
    {
        $rule = new DateRangeRule();

        $rule->assign([
            'fromDate' => '2024-01-15T10:30:45',
            'toDate' => '2024-01-31T23:59:59',
            'useTime' => true,
            'timezone' => 'UTC',
        ]);

        $scopeMock = $this->createMock(RuleScope::class);
        $scopeMock->method('getCurrentTime')->willReturn(new \DateTimeImmutable('2024-01-20 12:00:00'));

        $result = $rule->match($scopeMock);

        static::assertTrue($result);
    }

    /**
     * @param array<string, string|bool|\DateTime> $options
     */
    #[DataProvider('provideInvalidDateAndTimezoneFormats')]
    public function testAssignPreservesInvalidFormatsForValidators(array $options): void
    {
        $rule = new DateRangeRule();

        $rule = $rule->assign($options);

        $fromDate = (new \ReflectionProperty(DateRangeRule::class, 'fromDate'))->getValue($rule);
        $toDate = (new \ReflectionProperty(DateRangeRule::class, 'toDate'))->getValue($rule);
        $useTime = (new \ReflectionProperty(DateRangeRule::class, 'useTime'))->getValue($rule);
        $timezone = (new \ReflectionProperty(DateRangeRule::class, 'timezone'))->getValue($rule);

        $result = [
            'fromDate' => $fromDate,
            'toDate' => $toDate,
            'useTime' => $useTime,
            'timezone' => $timezone,
        ];

        static::assertSame($options, $result);
    }

    public static function provideInvalidDateAndTimezoneFormats(): \Generator
    {
        yield 'invalid fromDate format' => [
            'options' => [
                'fromDate' => 'not-a-valid-date',
                'toDate' => '2024-12-31T23:59:59',
                'useTime' => true,
                'timezone' => null,
            ],
        ];

        yield 'invalid toDate format' => [
            'options' => [
                'fromDate' => new \DateTime('2024-01-01T00:00:00'),
                'toDate' => 'invalid-to-date',
                'useTime' => false,
                'timezone' => null,
            ],
        ];

        yield 'invalid timezone' => [
            'options' => [
                'fromDate' => new \DateTime('2024-01-01T00:00:00'),
                'toDate' => new \DateTime('2024-12-31T23:59:59'),
                'useTime' => true,
                'timezone' => 'not-a-valid-timezone',
            ],
        ];

        yield 'all invalid values' => [
            'options' => [
                'fromDate' => 'invalid-from',
                'toDate' => 'invalid-to',
                'useTime' => false,
                'timezone' => null,
            ],
        ];
    }

    /**
     * @param array<string, string> $properties
     */
    #[DataProvider('provideInvalidStringValuesForMatch')]
    public function testMatchThrowsExceptionWhenDatePropertiesAreStrings(array $properties): void
    {
        $rule = new DateRangeRule(...$properties);

        $this->expectExceptionObject(
            RuleException::invalidDateRangeUsage('fromDate, toDate and timezone cannot be a string at this point')
        );

        $rule->match($this->createMock(RuleScope::class));
    }

    public static function provideInvalidStringValuesForMatch(): \Generator
    {
        yield 'fromDate is string' => [
            'properties' => [
                'fromDate' => '2024-01-15T10:30:45',
            ],
        ];

        yield 'toDate is string' => [
            'properties' => [
                'toDate' => '2024-12-31T23:59:59',
            ],
        ];

        yield 'timezone is string' => [
            'properties' => [
                'timezone' => 'Europe/Berlin',
            ],
        ];

        yield 'all properties are strings' => [
            'properties' => [
                'fromDate' => '2024-01-15T10:30:45',
                'toDate' => '2024-12-31T23:59:59',
                'timezone' => 'UTC',
            ],
        ];

        yield 'fromDate and toDate are strings' => [
            'properties' => [
                'fromDate' => '2024-01-15T10:30:45',
                'toDate' => '2024-12-31T23:59:59',
            ],
        ];
    }
}
